// Global namespace
var MasterStroke = MasterStroke || {};
MasterStroke.ViewModels = MasterStroke.ViewModels || {};

define
(    
    ["require", 
     "lib/knockout",
     "Messages/JoinDocumentRequest", 
     "Messages/JoinDocumentResponse",
     "Messages/UserStatusChangedNotification",
     "Messages/ElementCreateAction",
     "Messages/ElementDeleteAction",
     "Messages/ElementModifyAction",
     "Messages/FrameCreateAction",
     "Messages/FrameDeleteAction",
     "Messages/FrameReorderAction",
     "Messages/TaskCreateAction",
     "Messages/AddSoundAction",
     "Messages/DocumentChangeRequest",
     "Messages/DocumentChangeResponse",
     "Messages/DocumentChangedNotification",
     "Messages/ElementCreateResult",
     "Messages/FrameCreateResult",
     "Messages/TaskCreateResult",
     "Common/Validate",
     "../ShapeFactory",
     "../Task",
     "../Frame",
     "Common/OrderedChangeIdSet",
     "./NewTaskViewModel",
     "./AnimationViewModel",
     "lib/bootstrap-modal"],
    function(require, 
                 ko,                 
                 JoinDocumentRequest,
                 JoinDocumentResponse,
                 UserStatusChangedNotification,
                 ElementCreateAction,
                 ElementDeleteAction,
                 ElementModifyAction,
                 FrameCreateAction,
                 FrameDeleteAction,
                 FrameReorderAction,
                 TaskCreateAction,
                 AddSoundAction,
                 DocumentChangeRequest,
                 DocumentChangeResponse,
                 DocumentChangedNotification,
                 ElementCreateResult,
                 FrameCreateResult,
                 TaskCreateResult,
                 Validate,
                 ShapeFactory,
                 Task,
                 Frame,
                 OrderedChangeIdSet,
                 NewTaskViewModel,
                 AnimationViewModel)
    {        
        MasterStroke.ViewModels.DocumentViewModel = function(canvasId)
        {
            var _socket = io.connect();

            // Retreieve document id
            var _documentId = document.location.pathname.substr(1);

            if (!_documentId)
            {
                throw "Invalid document id";
            }

            var _that = this;
            var _sessionId;
            var _canvas;
            var _localChanges = {};
            var _localChangeId = 1;

            this.userName = ko.observable();
            this.soundUrl = ko.observable();
            this.isDocumentVisible = ko.observable(false);
            var _tasks = new OrderedChangeIdSet();
            this.tasks = ko.observableArray(_tasks.bindingSource());            
            this.selectedTask = ko.observable();
            this.strokeThickness = ko.observable(5);
            this.minStrokeWidth = ko.observable(1);
            this.maxStrokeWidth = ko.observable(30);
            this.selectedTab = ko.observable("homeTab");
            this.showReferences = ko.observable(true);
            this.showAnnotations = ko.observable(false);
            this.newTaskViewModel = new NewTaskViewModel();            

            this.strokeColor = ko.computed(
            {
                read: function(){return _canvas ? _canvas.freeDrawingColor || "#000000" : "#000000";}, 
                write: function(value) {_canvas.freeDrawingColor = value;},
                owner: _that
            });            

            this.referenceFrames = ko.computed(
            {
                read: getReferenceFrames
            });

            this.start = function()
            {
                _canvas = setupCanvas(canvasId);
                subscribeCanvasToEvents();                

                $(document).ready(function()
                {
                    _socket.on('connect', handleConnected);
                });
            };

            this.joinDocument = function()
            {
                sendJoinMessage(this.userName());
            };

            this.selectPenTool = function()
            {                
                _canvas.isDrawingMode = true;
                var upperCanvas = $(_canvas.upperCanvasEl);
                upperCanvas.css("cursor", "crosshair");
            };

            this.selectSelectionTool = function()
            {
                _canvas.isDrawingMode = false;
               var upperCanvas = $(_canvas.upperCanvasEl);
                upperCanvas.css("cursor", "default");
            }

            this.showAddNewTaskDialog = function()
            {
                $("#addTaskDialog").modal();
            }            

            this.addNewTask = function()
            {
                // TODO validate task parameters here...
                var name = this.newTaskViewModel.name();
                var description = this.newTaskViewModel.description();

                var selectedTask = this.selectedTask();

                var predecessorTaskId;
                if (selectedTask)
                {
                    predecessorTaskId = selectedTask.changeId;
                }

                var task = new MasterStroke.Task(_localChangeId++, name, description);
                addLocalChange(task, createTaskSucceeded);
                _tasks.add(predecessorTaskId, task);
                _that.tasks.valueHasMutated();

                if (this.tasks().length == 1)
                {                    
                    this.selectedTab("homeTab");
                }

                this.newTaskViewModel.name("");
                this.newTaskViewModel.description("");
                hideAddNewTaskDialog();
                this.selectedTask(task);
                emitDocumentChangeAction(new TaskCreateAction(predecessorTaskId, task));                
            };

            this.deleteTask = function(task)
            {

            };

            this.addNewFrame = function()
            {
                addNewFrameInner(new Frame(_localChangeId++));
            };

            this.deleteFrame = function(frame)
            {
                frame = frame ? frame : getSelectedFrame();

                if (!frame)
                {
                    return;                    
                }

                _that.selectedTask().deleteFrame(frame.changeId);
                emitDocumentChangeAction(new FrameDeleteAction(_that.selectedTask().changeId, frame.changeId));
            }

            this.cloneFrame = function(frame)
            {
                frame = frame || getSelectedFrame();

                if (!frame)
                {
                    return;
                }

                addNewFrameInner(frame.clone(_localChangeId++));
            }

            this.reorderFrame = function(reorderInfo)
            {                
                if (!reorderInfo)
                {
                    return;
                }

                var frame = reorderInfo.item;                
                var newIndex = reorderInfo.targetIndex;

                emitDocumentChangeAction(new FrameReorderAction(_that.selectedTask().changeId, frame.changeId, newIndex));
            }

            this.selectTask = function(task)
            {
                _that.selectedTask(task);
            }

            this.selectFrame = function(frame)
            {
                var selectedFrame = getSelectedFrame();

                if ((!frame) || (selectedFrame === frame))
                {
                    return;
                }

                _canvas.discardActiveGroup();
                _canvas.discardActiveObject();
                
                _that.selectedTask().selectedFrame(frame);
                frame.select();
            };

            this.deleteShapes = function()
            {
                var selectedShapes = getSelectedShapes();

                if (!selectedShapes.length)
                {
                    return;
                }

                var sharedSelectedShapes = getSharedShapes(selectedShapes);

                var deletedChangeIds = [];
                var sharedShapesMap = {};
                for (var i = 0; i < sharedSelectedShapes.length; i++)
                {
                    deletedChangeIds.push(sharedSelectedShapes[i].changeId);
                    sharedShapesMap[sharedSelectedShapes[i]] = 1;
                }

                var selectedFrame = this.selectedTask().selectedFrame();

                // Delete images (non sharable)
                for (var i = 0; i < selectedShapes.length; i++)
                {
                    if (!sharedShapesMap[selectedShapes[i]])
                    {
                        selectedFrame.images.remove(selectedShapes[i]);
                    }
                }

                var selectedFrameId = selectedFrame.changeId;

                deleteShapesInner(this.selectedTask().changeId, selectedFrameId, deletedChangeIds);

                emitDocumentChangeAction(new ElementDeleteAction(this.selectedTask().changeId, selectedFrameId, deletedChangeIds));
            };

            function getNonSharedShapes(shapes)
            {
                var allSharedShapes = _that.selectedTask().selectedFrame().shapes();
                var allSharedShapesMap = {};
                for (var i = 0; i < allSharedShapes.length; i++)
                {
                    allSharedShapesMap[allSharedShapes[i].getShape()] = 1;
                }

                var nonSharedShapes = [];
                for (var i = 0; i < shapes.length; i++)
                {
                    if (allSharedShapesMap[shapes[i]])
                    {
                        sharedShapes.push(shapes[i]);
                    }
                }

                return sharedShapes;
            }

            function getSharedShapes(shapes)
            {
                var allSharedShapes = _that.selectedTask().selectedFrame().shapes();
                var allSharedShapesMap = {};
                for (var i = 0; i < allSharedShapes.length; i++)
                {
                    allSharedShapesMap[allSharedShapes[i].getShape()] = 1;
                }

                var sharedShapes = [];
                for (var i = 0; i < shapes.length; i++)
                {
                    if (allSharedShapesMap[shapes[i]])
                    {
                        sharedShapes.push(shapes[i]);
                    }
                }

                return sharedShapes;
            }

            this.isStartDisabled = ko.computed(function()
            {                
                return (getSelectedFrameIndex() < 1) ? true : false;
            });

            this.gotoStart = function()
            {
                if (this.isStartDisabled())
                {
                    return;
                }

                var frames = _that.selectedTask().frames();
                _that.selectedTask().selectedFrame(frames[0]);                                
            };

            this.isPreviousDisabled = ko.computed(function()
            {
                return getSelectedFrameIndex() <= 0;
            });

            this.gotoPreviousFrame = function()
            {
                if (this.isPreviousDisabled())
                {
                    return;
                }

                _that.selectedTask().selectedFrame((_that.selectedTask().frames()[getSelectedFrameIndex() - 1]));
            };            

            this.isPlayDisabled = function()
            {
                return getSelectedFrameIndex() < 0;
            };

            this.playTask = function()
            {
                if (_that.isPlayDisabled())
                {
                    return;
                }

                var viewModel = new AnimationViewModel(_that.selectedTask().frames(), hideFullScreenContent);
                displayPageInFullScreen("Pages/animation.html", viewModel);
            };

            this.playMovie = function()
            {
                if (_that.isPlayDisabled())
                {
                    return;
                }

                var viewModel = new AnimationViewModel(getMovieFrames(), hideFullScreenContent, null, _that.soundUrl());
                displayPageInFullScreen("Pages/animation.html", viewModel);
            }

            this.isNextDisabled = function()
            {
                var selectedFrameIndex = getSelectedFrameIndex();
                var selectedTask = _that.selectedTask();
                return (!selectedTask) || (selectedFrameIndex >= (selectedTask.frames().length - 1)) || (selectedFrameIndex < 0);
            }

            this.gotoNextFrame = function()
            {
                if (this.isNextDisabled())
                {
                    return;
                }

                _that.selectedTask().selectedFrame(_that.selectedTask().frames()[getSelectedFrameIndex() + 1]);
            };

            this.isLastDisabled = function()
            {
                return _that.isNextDisabled();
            };

            this.gotoLastFrame = function()
            {
                if (this.isLastDisabled())
                {
                    return;
                }
                
                var frames = _that.selectedTask().frames();
                _that.selectedTask().selectedFrame(frames[frames.length - 1]);
            };

            this.setFrameAsReference = function(frame)
            {
                var selectedFrame = getSelectedFrame();

                if ((!frame)||
                    (!selectedFrame)||
                    (frame === selectedFrame))
                {
                    return;
                }

                var referenceFrames = selectedFrame.referenceFrames();

                if (referenceFrames.indexOf(frame) < 0)
                {
                    selectedFrame.addReferenceFrame(frame);
                }
            };

            this.deleteFrameReference = function(frame)
            {
                var selectedFrame = getSelectedFrame();
                selectedFrame.removeReferenceFrame(frame.changeId);
            };

            this.addImage = function()
            {
                var fileSelector = document.createElement('input');
                fileSelector.setAttribute('type', 'file');

                fileSelector.onchange = function()
                {
                    var files = fileSelector.files;
                    for (i = 0; i < files.length; i++)
                    {
                       if ((!files[i].type) || (!files[i].type.match(/image+/)))
                       {
                           continue;
                       }
                           
                       var reader = new FileReader();
                       reader.onload = (function (theFile)
                       {
                           return function (e)
                           {
                                var imgElement = document.createElement("img");
                                imgElement.onload = function()
                                {
                                    var img = new fabric.Image(imgElement);
                                    
                                    img.clone = function()
                                    {
                                        var clonedImage = new fabric.Image(imgElement);
                                        for (var property in this)
                                        {
                                            clonedImage[property] = this[property];
                                        }
                                        return clonedImage;
                                    };
                                    
                                    _that.selectedTask().selectedFrame().images.push(img);
                                };
                                imgElement.src = e.target.result;
                           };
                       })(files[i]);
    
                       // Read in the image file as a data URL.
                       reader.readAsDataURL(files[i]);
                   }
                };

                fileSelector.click();                
            };

            this.sendToBack = function()
            {
                var selectedShapes = getSelectedShapes();

                if (!selectedShapes.length)
                {
                    return;
                }
                
                for (var i = 0; i < selectedShapes.length; i++)
                {
                    _canvas.sendToBack(selectedShapes[i]);
                }
            };

            this.bringToFront = function()
            {
                var selectedShapes = getSelectedShapes();

                if (!selectedShapes.length)
                {
                    return;
                }
                
                for (var i = 0; i < selectedShapes.length; i++)
                {
                    _canvas.bringToFront(selectedShapes[i]);
                }
            };

            this.showAddSoundDialog = function()
            {
                $("#addSoundDialog").modal();
            };

            this.addSound = function()
            {                
                emitDocumentChangeAction(new AddSoundAction(this.soundUrl()));
                $("#addSoundDialog").modal('hide');
            }

            function getMovieFrames()
            {
                var frames = [];
                var tasks = _that.tasks();
                for (var i = 0; i < tasks.length; i++)
                {
                    var taskFrames = tasks[i].frames();
                    for (var j = 0; j < taskFrames.length; j++)
                    {
                        frames.push(taskFrames[j]);
                    }
                }
                return frames;
            }

            function addNewFrameInner(frame)
            {
                var selectedFrame = getSelectedFrame();

                var predecessorFrameId;
                var selectedTask = _that.selectedTask();
                var newFrameIndex = selectedTask.frames().indexOf(selectedFrame) + 1;
                if (newFrameIndex > 0)
                {
                    predecessorFrameId = selectedTask.frames()[newFrameIndex - 1].changeId;
                }
                
                var shapes = frame.shapes();
                for (var i = 0; i < shapes.length; i++)
                {
                    shapes[i].getShape().changeId = _localChangeId++;
                }

                addLocalChange(frame, createFrameSucceeded);
                _that.selectedTask().addFrame(predecessorFrameId, frame);
                _that.selectFrame(frame);

                emitDocumentChangeAction(new FrameCreateAction(selectedTask.changeId, frame, predecessorFrameId));                
            }

            function getSelectedFrame()
            {
                return _that.selectedTask() ? _that.selectedTask().selectedFrame() : null;
            }

            function getSelectedFrameIndex()
            {
                var selectedTask = _that.selectedTask();

                if (!selectedTask)
                {
                    return -1;
                }

                return selectedTask.frames().indexOf(getSelectedFrame());
            }

            function deleteShapesInner(taskChangeId, frameChangeId, deleteChangeIds)
            {
                var frame = getFrameByChangeId(taskChangeId, frameChangeId);
                
                if (!frame)
                {
                    return;
                }

                var isFrameSelected = (frame === _that.selectedTask().selectedFrame());

                for (var i = 0; i < deleteChangeIds.length; i++)
                {
                    var deletedShapeId = deleteChangeIds[i];

                    if (isFrameSelected)
                    {
                        var deletedShape = frame.getShape(deletedShapeId);
                        _canvas.remove(deletedShape);
                    }

                    frame.deleteShape(deletedShapeId);                    
                }

                if (isFrameSelected)
                {
                    _canvas.discardActiveGroup();
                    _canvas.renderAll();
                }
            }

            function modifyShapes(taskChangeId, frameChangeId, elements)
            {
                var frame = getFrameByChangeId(taskChangeId, frameChangeId);
                
                if (!frame)
                {
                    return;
                }

                var shapes = [];
                var activeGroup = _canvas.getActiveGroup();

                for (var i = 0; i < elements.length; i++)
                {
                    var changeId = elements[i].changeId;

                    if (activeGroup)
                    {
                        var existingShape = frame.getShape(changeId);
                        if (existingShape)
                        {
                            activeGroup.removeWithUpdate(existingShape.getShape());
                        }
                    }

                    createShape(changeId,
                                        elements[i], 
                                        function(shape)
                                        {
                                            shapes.push(shape);

                                            if (shapes.length === elements.length)
                                            {
                                                frame.updateShapes(shapes);
                                                _that.selectedTask().selectedFrame.valueHasMutated();
                                            }
                                        });
                }

                if ((activeGroup) && (!activeGroup.getObjects().length))
                {
                    _canvas.discardActiveGroup();
                }                
            }

            function handleConnected()
            {
                _socket.on(JoinDocumentResponse.id, handleDocumentJoinResponse);
                _socket.on(UserStatusChangedNotification.id, handleUserStatusChanged);
                _socket.on(DocumentChangeResponse.id, handleDocumentChangeResponse);
                _socket.on(DocumentChangedNotification.id, handleDocumentChangeNotification);
                sendJoinMessage();
            }

            function showJoinDialog()
            {
                $("#joinDialog").modal();
            }

            function hideJoinDialog()
            {
                $("#joinDialog").modal('hide');
            }

            function sendJoinMessage(userName)
            {
                // Perform handshake                
                var message = new JoinDocumentRequest(_documentId, userName);                
                _socket.emit(message.id, message);
            }

            function handleDocumentJoinResponse(msg)
            {
                switch(msg.result)
                {
                    case "identify":
                        showJoinDialog();
                    break;

                    case "recognized":                    
                        hideJoinDialog();
                        _that.isDocumentVisible(true);
                        _that.selectPenTool();
                        _sessionId = msg.sessionId;

                        updateDocument(msg.document);                        
                        
                    break;
                }
            }

            function getIndexOfPredecessorFrame(predecessorFrameId)
            {
                var frames = _that.selectedTask().frames();

                for (var i = 0; i < frames.length; i++)
                {
                    if (predecessorFrameId === frames[i].changeId)
                    {
                        return i;
                    }
                }
                return -1;
            }

            function getFrameByChangeId(taskChangeId, frameChangeId)
            {
                var task = _tasks.find(taskChangeId);
                var frame = task ? task.getFrame(frameChangeId) : null;
                return frame;
            }

            function updateDocument(doc)
            {
                _that.soundUrl(doc.soundUrl);
                var predecessorTaskId;
                for (var i = 0; i < doc.tasks.length; i++)
                {
                    var taskObj = doc.tasks[i];
                    var task = new MasterStroke.Task(taskObj.changeId, taskObj.name, taskObj.description);

                    var framesObj = taskObj.frames;
                    var predecessorFrameId;
                    for (var j = 0; j < framesObj.length; j++)
                    {
                        var frameObj = framesObj[j];
                        var frame = new MasterStroke.Frame(frameObj.changeId);

                        var shapesObj = frameObj.shapes;
                        for (var k = 0; k < shapesObj.length; k++)
                        {
                            var shapeObj = shapesObj[k];
                            var thisFrame = frame;
                            var thisShapeObj = shapeObj;
                            createShape(shapeObj.changeId, 
                                                shapeObj,
                                                function(shape)
                                                {                                                    
                                                    thisFrame.addShape(shape, false);
                                                    if (thisFrame.shapes().length == thisShapeObj.length)
                                                    {
                                                        thisFrame.shapes.valueHasMutated();
                                                    }
                                                });                            
                        }
                        
                        task.addFrame(predecessorFrameId, frame);
                        predecessorFrameId = frameObj.changeId;
                    }
                    
                    _tasks.add(predecessorTaskId, task);
                    predecessorTaskId = taskObj.changeId;
                    _that.tasks.valueHasMutated();
                }
                
                var tasks = _that.tasks();
                if (tasks.length)
                {
                    _that.selectedTask(tasks[0]);

                    if (tasks[0].frames().length)
                    {
                        _that.selectedTask().selectedFrame(tasks[0].frames()[0]);                        
                    }                    
                }
                else
                {
                    _that.selectedTab("tasksTab");
                    
                    if (doc.owner.sessionId === _sessionId)
                    {
                        showFirstTimeNoTaskMessage();
                    }
                    else
                    {
                        showNoTaskCreatedByLeaderMessage();
                    }
                }
            }            

            function showFirstTimeNoTaskMessage()
            {
                displayPageInFullScreen("Pages/first-time-task-message.html", _that);
            }

            function showNoTaskCreatedByLeaderMessage()
            {
                displayPageInFullScreen("Pages/no-task-created-by-leader-message.html", _that);
            }            

            function displayPageInFullScreen(url, dataSource)
            {
                var $fullScreenLayer = $("<div class='full-screen'/>");                
                
                $('body').append($fullScreenLayer);

                $fullScreenLayer.load(url, function()
                {
                    ko.applyBindings(dataSource, $fullScreenLayer[0]);
                });
            }

            function handleUserStatusChanged(msg)
            {
                var i = 0;
            }

            function handleDocumentChangeResponse(msg)
            {
                if (!Validate.paramsNotNull([msg, msg.result, msg.result.localChangeId]))
                {
                    return;
                }

                var localChange = _localChanges[msg.result.localChangeId];

                if (!Validate.paramsNotNull([localChange, localChange.handler, localChange.object]))
                {
                    return;
                }

                localChange.handler(msg, localChange.object);
                delete _localChanges[msg.result.localChangeId];
            }

            function handleDocumentChangeNotification(msg)
            {
                if (!Validate.paramsNotNull([msg, msg.documentId, msg.changeAction]))
                {
                    return;
                }

                if (msg.documentId !== _documentId)
                {
                    return;
                }

                switch(msg.changeAction.id)
                {
                    case ElementCreateAction.id:

                        var element = msg.changeAction.element;

                        if (!element)
                        {
                            return;
                        }

                        var frame = getFrameByChangeId(msg.changeAction.taskChangeId, msg.changeAction.frameChangeId);

                        if (!frame)
                        {
                            return;
                        }
                        
                        addElement(msg.changeId, element, frame);

                    break;

                    case ElementDeleteAction.id:

                        deleteShapesInner(msg.changeAction.taskChangeId, msg.changeAction.frameChangeId, msg.changeAction.changeIds);

                    break;

                    case ElementModifyAction.id:

                        modifyShapes(msg.changeAction.taskChangeId, msg.changeAction.frameChangeId, msg.changeAction.elements);

                    break;

                    case TaskCreateAction.id:

                        var task = msg.changeAction.task;

                        if (!task)
                        {
                            return;
                        }

                        var newTask = new Task(task.changeId, task.name, task.description);
                        _tasks.add(msg.changeAction.predecessorTaskId, newTask);
                        _that.tasks.valueHasMutated();

                        if (_that.tasks().length === 1)
                        {
                            _that.selectedTask(newTask);
                            _that.selectedTab("homeTab");                            
                        }

                        hideFullScreenContent();

                    break;

                    case FrameCreateAction.id:

                        var task = _tasks.find(msg.changeAction.taskChangeId);
                        var newFrame = new Frame(msg.changeId);
                        task.addFrame(msg.changeAction.predecessorFrameId, newFrame);
                        
                        var shapesObj = msg.changeAction.frame.shapes;
                        for (var i = 0; i < shapesObj.length; i++)
                        {
                            var shapeObj = shapesObj[i];
                            addElement(shapeObj.changeId, shapeObj, newFrame);
                        }

                        if (task.frames().length === 1)
                        {
                            task.selectedFrame(newFrame);
                        }

                    break;

                    case FrameDeleteAction.id:

                        var task = _tasks.find(msg.changeAction.taskChangeId);
                        var frame = task ? task.getFrame(msg.changeAction.frameChangeId) : null;

                        if (frame)
                        {
                            task.deleteFrame(frame.changeId);
                        }

                    break;

                    case FrameReorderAction.id:

                        var task = _tasks.find(msg.changeAction.taskChangeId);
                        if (task)
                        {
                            task.reorderFrame(msg.changeAction.frameChangeId, msg.changeAction.newIndex);  
                        }

                    break;

                    case AddSoundAction.id:
                        _that.soundUrl(msg.changeAction.soundUrl);
                    break;
                }
            }

            function addElement(changeId, obj, frame)
            {
                createShape(changeId, obj, function(shape) {frame.addShape(shape, false);});                
            }

            function createShape(changeId, obj, loadFunc)
            {
                var onLoad = function(shape)
                {
                    initializeShape(shape, changeId, obj.annotation);
                    
                    if (loadFunc)
                    {
                        loadFunc(shape);
                    }
                };

                ShapeFactory.createShape(obj, onLoad);                
            }

            function setupCanvas(canvasId)
            {                
                var canvas = new fabric.Canvas(canvasId);
                canvas.setWidth(2000);
                canvas.setHeight(2000);

                canvas.on("path:created", onPathCreated);
                canvas.on("object:modified", onObjectModified);

                // Fixes the mouse offset problem in fabricjs
                canvas.on("after:render", function(){canvas.calcOffset();});                

                return canvas;
            }

            function subscribeCanvasToEvents()
            {
                // Change in selectedFrame
                ko.computed(refreshCanvas, _that);

                // Change in stroke thickness
                ko.computed(function()
                {
                    _canvas.freeDrawingLineWidth = parseInt(this.strokeThickness(), 10) || 1;
                }, _that);                
            }

            function refreshCanvas()
            {
                _canvas.clear();
                    
                var selectedTask = _that.selectedTask();
                var selectedFrame = selectedTask ? selectedTask.selectedFrame() : null;

                if (selectedFrame)
                {
                    var images = selectedFrame.images();
                    for (var i = 0; i < images.length; i++)
                    {
                        _canvas.add(images[i]);
                    }

                    var shapes = selectedFrame.shapes();

                    var showAnnotations = _that.showAnnotations();

                    if (_that.showReferences())
                    {
                        var refrenceFrames = getReferenceFrames();

                        for (var i = 0; i < refrenceFrames.length; i++)
                        {
                            var referenceImages = refrenceFrames[i].images();
                            for (var j = 0; j < referenceImages.length; j++)
                            {
                                var referenceImage = referenceImages[j].clone();
                                referenceImage.opacity = 0.1;
                                referenceImage.selectable = false;
                                _canvas.add(referenceImage);
                            }

                            var referenceShapes = refrenceFrames[i].shapes();

                            for (var j = 0; j < referenceShapes.length; j++)
                            {
                                if (!referenceShapes[j].getShape().annotation)
                                {                                    
                                    var referenceShape = referenceShapes[j].getShape().clone();
                                    referenceShape.opacity = 0.1;
                                    referenceShape.selectable = false;
                                    _canvas.add(referenceShape);
                                }
                            }
                        }
                    }                    

                    for (var i = 0; i < shapes.length; i++)
                    {
                        var shape = shapes[i].getShape();

                        if ((!shape.annotation) || (showAnnotations))
                        {
                            _canvas.add(shapes[i].getShape());
                        }                        
                    }
                    
                    _canvas.renderAll();
                }
            }

            function getReferenceFrames()
            {
                var selectedTask = _that.selectedTask();
                var selectedFrame = selectedTask ? selectedTask.selectedFrame() : null;

                if (!selectedFrame)
                {
                    return [];
                }

                var referenceFrames = selectedFrame.referenceFrames();

                if (!referenceFrames.length)
                {
                    var frameIndex = _that.selectedTask().frames().indexOf(selectedFrame);

                    if (frameIndex > 0)
                    {
                        referenceFrames = [];
                        referenceFrames.push(_that.selectedTask().frames()[frameIndex - 1]);
                    }
                }

                return referenceFrames;                
            }

            function onPathCreated(e)
            {
                var path = e.path;

                initializeShape(path, _localChangeId++, _that.showAnnotations());
                
                addLocalChange(path, createSucceeded);                
                _that.selectedTask().selectedFrame().addShape(path, true);
                emitDocumentChangeAction(new ElementCreateAction(_that.selectedTask().changeId, _that.selectedTask().selectedFrame().changeId, path));                
            }

            function initializeShape(element, changeId, annotation)
            {
                element.changeId = changeId;
                element.annotation = annotation;

                element.toObject = (function(toObject)
                {
                    return function()
                    {
                        return fabric.util.object.extend(toObject.call(this), {changeId: this.changeId, annotation: this.annotation});
                    }
                }(element.toObject));
            }

            function onObjectModified(e)
            {
                var modifiedShapes = getSharedShapes(getSelectedShapes());

                if (!modifiedShapes.length)
                {
                    return;
                }

                if (e.target.type === "group")
                {                    
                    var left = e.target.left;
                    var top = e.target.top;

                    var clonedShapes = [];
                    var group = _canvas.getActiveGroup();
                    for (var i = 0; i < modifiedShapes.length; i++)
                    {
                        var modifiedShape = fabric.util.object.clone(modifiedShapes[i]);
                        modifiedShape.left += left;
                        modifiedShape.top += top;
                        clonedShapes.push(modifiedShape);                        
                    }

                    modifiedShapes = clonedShapes;                    
                }

                _that.selectedTask().selectedFrame().shapes.valueHasMutated();
                emitDocumentChangeAction(new ElementModifyAction(_that.selectedTask().changeId, _that.selectedTask().selectedFrame().changeId, modifiedShapes));
            }

            function createTaskSucceeded(msg, obj)
            {
                if (msg.error)
                {
                    // TODO: Handle error here...
                    return;
                }

                _tasks.updateChangeId(obj, msg.result.changeId);
            }

            function createFrameSucceeded(msg, obj)
            {
                if (msg.error)
                {
                    // TODO: Handle error here...
                    return;
                }

                var task = _tasks.find(msg.result.taskChangeId);

                if (!task)
                {
                    // TODO: Handle error here...
                    return;
                }

                var frame = task.getFrame(msg.result.localChangeId);
                if (!frame)
                {
                    return;
                }

                task.updateFrameChangeId(obj, msg.result.changeId);

                var shapes = frame.shapes();
                for (var i = 0; i < shapes.length; i++)
                {
                    var shape = shapes[i].getShape();
                    var localShapeChangeId = shape.changeId;
                    shape.changeId = msg.result.shapeChangeIds[localShapeChangeId];
                }
            }

            function createSucceeded(msg, obj)
            {
                if (msg.error)
                {
                    // TODO: Handle error here...
                    return;
                }
                
                obj.changeId = msg.result.changeId;
            }

            function addLocalChange(obj, handler)
            {                
                _localChanges[obj.changeId] = {object: obj, handler: handler};
            }

            function getSelectedShapes()
            {
                var selectedObjects = [];

                var activeGroup = _canvas.getActiveGroup();
                if (activeGroup)
                {
                    var activeObjects = activeGroup.objects;

                    if (activeObjects)
                    {
                        for (var i = 0; i < activeObjects.length; i++)
                        {
                            selectedObjects.push(activeObjects[i]);
                        }
                    }                    
                }

                var activeObject = _canvas.getActiveObject();

                if (activeObject && (!selectedObjects.length))
                {
                    selectedObjects.push(activeObject);
                }                

                return selectedObjects;
            }

            function emitDocumentChangeAction(changeAction)
            {
                var changeMsg = new DocumentChangeRequest(_documentId, changeAction);
                _socket.emit(changeMsg.id, changeMsg);
            }

            function hideAddNewTaskDialog()
            {
                $("#addTaskDialog").modal('hide');
                hideFullScreenContent();
            }

            function hideFullScreenContent()
            {
                $(".master-stroke .full-screen").remove();
            }
        };

        return MasterStroke.ViewModels.IndexViewModel;        
    }
);